package org.orm.hsc;

import org.orm.hsc.annotations.Delete;
import org.orm.hsc.annotations.Insert;
import org.orm.hsc.annotations.Select;
import org.orm.hsc.annotations.Update;
import org.orm.hsc.exceptions.ConfigFileNotFoundException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author
 * @date 2025-04-11
 * @Description 用于读取配置信息
 */
public class Configuration {
    private String driver;
    private String url;
    private String username;
    private String password;

    // 根据配置文件中
    private Map<String, SqlHandler> sqlHandlerMap = new HashMap<>();

    /**
     * 版本一：支持在classpath目录中读取配置文件 <br/>
     * new Configuration("com/mysql-jdbc.properties");
     *
     * @param classpathFile
     */
    public Configuration(String classpathFile) {
        // 加载配置文件
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(classpathFile);
        if (inputStream == null) {
            throw new ConfigFileNotFoundException("配置文件不存在");
        }
        // 读取配置文件
        readConfig(inputStream);
    }

    /**
     * 版本二：支持任意路径下的配置文件<br/>
     * File file =new File("f:/z/oracle-jdbc.properties); <br/>
     *
     * @param file
     */
    public Configuration(File file) {
        try {
            if (!file.exists()) {
                throw new ConfigFileNotFoundException("配置文件不存在");
            }
            FileInputStream fileInputStream = new FileInputStream(file);
            readConfig(fileInputStream);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取配置文件数据库连接信息
     *
     * @param inputStream
     */
    private void readConfig(InputStream inputStream) {
        Properties properties = new Properties();
        try {
            // 加载配置文件
            properties.load(inputStream);
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            String sqlMapper = properties.getProperty("sql-mapper");
            executorSqls(sqlMapper);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private void executorSqls(String sqlMapper) {
        if (sqlMapper != null && "".equals(sqlMapper)) {
            return;
        }
        // 处理空格 并且获取返回的类名数组 com.sql.CarSql,com.sql.UserSql
        String[] interfaceNames = sqlMapper.replaceAll(" ", "").split(",");
        for (String interfaceName : interfaceNames) {
            // 根据类名获取类
            try {
                Class<?> aClass = Class.forName(interfaceName);
                // 获取类中的方法
                executeSql(aClass);
                // 获取方法
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

    private void executeSql(Class<?> aClass) {
        //找到接口中所有的方法
        //获得方法上定义的注解 （如果没有就略过）
        //需要针对于不同的sql注解，获得更多的信息 （insert-propertyName , select-returnType）
        Method[] methods = aClass.getMethods();
        // 获取类上的所有的方法
        for (Method method : methods) {
            Annotation annotation;
            // annotation 既赋值也判断
            if ((annotation = method.getAnnotation(Insert.class)) != null) {
                //方法有Insert 注解 需要额外考虑propertyName问题
                // 获取注解对象
                Insert insert = (Insert) annotation;
                // 获取注解上的value值
                // @Insert(value="insert into t_car values(null,#{cname},#{color},#{price})",propertyName = "cno")
                String sql = insert.value();
                // 获取注解上的propertyName值
                String propertyName = insert.propertyName();
                // 处理sql语句占位符
                SqlHandler sqlHandler = new SqlHandler();
                sqlHandler.executeSql(sql);
                // 并且设置propertyName
                sqlHandler.setPropertyName(propertyName);
                // 通过将类名和方法名用.连接起来，生成了一个唯一的key com.sql.CarSql.save
                String key = aClass.getName() + "." + method.getName();
                sqlHandlerMap.put(key, sqlHandler);
            } else if ((annotation = method.getAnnotation(Update.class)) != null) {
                // 处理update 注解
                Update update = (Update) annotation;
                String sql = update.value();
                SqlHandler sqlHandler = new SqlHandler();
                sqlHandler.executeSql(sql);
                String key = aClass.getName() + "." + method.getName();
                sqlHandlerMap.put(key, sqlHandler);
            } else if ((annotation = method.getAnnotation(Delete.class)) != null) {
                // 方法有delete 注解
                Delete delete = (Delete) annotation;
                String sql = delete.value();
                SqlHandler sqlHandler = new SqlHandler();
                sqlHandler.executeSql(sql);
                String key = aClass.getName() + "." + method.getName();
                sqlHandlerMap.put(key, sqlHandler);
            } else if ((annotation = method.getAnnotation(Select.class)) != null) {
                //   @Select("select * from t_car where cno = #{cno}")
                //    Car findById();
                // 方法上有Select注解，需要额外考虑返回类型
                Select select = (Select) annotation;
                // "select * from t_car where cno = #{cno}"
                String sql = select.value();
                SqlHandler sqlHandler = new SqlHandler();
                // 占位符替换
                sqlHandler.executeSql(sql);

                //还需要获得结果类型，也就是返回类型 Car 或 返回类型List中的泛型 List<Car>
                // 下面两种情况
                //     @Select("select * from t_car")
                //    List<Car> findAll();
                //
                //    @Select("select * from t_car where cno = #{cno}")
                //    Car findById();
                // 获取方法的返回类型
                Class<?> returnType = method.getReturnType();
                // 处理返回类型有返List的情况
                if (List.class.isAssignableFrom(returnType)){
                    //返回类型是一个List集合，我们需要的是泛型
                    //ParameterizedType表示带参数的类型，有泛型
                    ParameterizedType parameterizedType = (ParameterizedType) method.getGenericReturnType();
                    // getActualTypeArguments() 方法用于获取泛型参数的实际类型。
                    // 对于 List<Car>，它会返回一个数组，其中包含 Car 类型。
                    // 对于单参数泛型（如 List<Car>），数组的长度为 1，可以通过 [0] 访问唯一的泛型参数。
                    // 对于多参数泛型（如 Map<String, Integer>），数组的长度为 2，可以通过 [0] 和 [1] 分别访问两个泛型参数。
                    Class rowType = (Class) parameterizedType.getActualTypeArguments()[0];
                    sqlHandler.setRowType(rowType);
                }else {
                    // 处理不是list集合的情况
                    //目前我们只支持两种返回类型 ， domain ， List<domain>
                    sqlHandler.setRowType(returnType);
                }
                String key = aClass.getName() + "." + method.getName();
                sqlHandlerMap.put(key, sqlHandler);
            }


        }
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Configuration{" +
                "driver='" + driver + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public SqlHandler getHandler(String sqlid) {
        return sqlHandlerMap.get(sqlid);
    }
}
